package cn.legym.garp.gateway.traffic.dynamic.context;

import com.alibaba.csp.sentinel.property.DynamicSentinelProperty;
import com.alibaba.csp.sentinel.property.SentinelProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * create by pipisun on 2021/9/18
 * copyright © 2017-2021 Legym Technology Co.,Ltd. All rights reserve
 * d.
 * file description:
 * <p>
 * 为{@link SentinelProperty}增加AOP功能。将数据变更通知过程中，内部再次发生的数据变化加入队列，等待本轮变更通知完成后再触发，
 * 从而保证事件的发生顺序。
 * <p>
 * 设计目的：
 * {@link cn.legym.garp.gateway.traffic.dynamic.SentinelDatasourceWatcher} 向数据源中增加了自己的listener，
 * 当Sentinel数据发生变更，会调用{@link SentinelProperty#updateValue(Object)}方法，依次通知所有listener；
 * <p>
 * watcher的listner会在收到通知后，将本地规则和新的Sentinel规则重新编辑整合, 再次触发规则更改。
 * <p>
 * 为了保证事件发生的顺序，需要在一轮{@link SentinelProperty#updateValue(Object)}调用完成之后，再行触发本轮通知过程中
 * 发生的所有变化，从而保证事件的发生顺序。
 * <p>
 * 为什么选择动态代理:
 * 1. {@link SentinelProperty}非Spring托管bean，无法使用AOP;
 * <p>
 * 2. {@link SentinelProperty}的 listener 由{@link java.util.HashSet}维护，内部没有通知顺序的概念，
 * 无法保证watcher追加的listener最后被触发
 * <p>
 * last update by {} on {}
 * update description:
 */
@Slf4j
public class SentinelPropertyProxyFactory {

    public static SentinelProperty<?> create(SentinelProperty<?> property) {
        if (isProxyClass(property)) {
            return property;
        }
        try {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(property.getClass());
            enhancer.setCallback(new MyMethodInterceptor());
            SentinelProperty<?> ret = (SentinelProperty<?>) enhancer.create();
            copyDynamicSentinelPropertyValues(property, ret);
            return ret;
        } catch (Exception e) {
            log.error("", e);
        }
        return property;
    }

    /**
     * 拷贝DynamicSentinelProperty数据成员
     *
     * @param src  from
     * @param dest to
     */
    private static void copyDynamicSentinelPropertyValues(SentinelProperty<?> src, SentinelProperty<?> dest) {
        if (DynamicSentinelProperty.class.equals(src.getClass())) {
            Arrays.stream(DynamicSentinelProperty.class.getDeclaredFields())
                    .forEach(field -> {
                        try {
                            field.setAccessible(true);
                            field.set(dest, field.get(src));
                        } catch (Exception e) {
                            log.error("", e);
                        }
                    });
        }
    }

    private static boolean isProxyClass(SentinelProperty<?> property) {
        return Arrays.stream(property.getClass().getInterfaces()).noneMatch(i -> i.equals(SentinelProperty.class));
    }

    static class MyMethodInterceptor implements MethodInterceptor {

        private static final String INTERCEPT_METHOD_NAME = "updateValue";

        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            Object ret = methodProxy.invokeSuper(o, objects);
            if (INTERCEPT_METHOD_NAME.equals(method.getName())) {
                log.info("intercept {}", INTERCEPT_METHOD_NAME);
                // 本轮通知结束，触发通知过程中发生的数据变更
                PropertyUpdateEventQueue.execute();
            }
            return ret;
        }

    }
}
